import os
import ast
import importlib
import datetime
import logging
from typing import Tuple, Optional, Set
from PyQt5.QtWidgets import QMessageBox, QFileDialog
from logging.handlers import TimedRotatingFileHandler  # 显式导入



# 导入配置
from config.constants import (
    REQUIRED_LIBS, LOG_FILE, MIN_RECORD_SIZE,
    DEFAULT_LOG_KEEP_DAYS, RECORDING_DIR
)


# -------------------------- 日志初始化 --------------------------
def init_logging() -> logging.Logger:
    """初始化全局日志（文件+控制台双输出）"""
    # 确保日志目录存在
    if not os.path.exists(os.path.dirname(LOG_FILE)):
        os.makedirs(os.path.dirname(LOG_FILE))

    # 配置日志格式
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s')

    # 文件处理器（按天切割，保留30天）
    file_handler = logging.handlers.TimedRotatingFileHandler(
        LOG_FILE, when='D', interval=1, backupCount=DEFAULT_LOG_KEEP_DAYS, encoding='utf-8'
    )
    file_handler.setFormatter(formatter)

    # 控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)

    # 配置根日志
    logger = logging.getLogger('rpa_manager')
    logger.setLevel(logging.INFO)
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)

    return logger


# 初始化全局日志对象（所有模块可导入使用）
logger = init_logging()


# -------------------------- 依赖检查 --------------------------
def check_required_libs() -> bool:
    """检查所有依赖库是否安装，缺失则弹窗提示"""
    missing_libs: list[Tuple[str, str]] = []
    for import_name, pip_name in REQUIRED_LIBS:
        try:
            # 导入库（仅需导入顶层模块）
            importlib.import_module(import_name.split('.')[0])
        except ImportError:
            missing_libs.append((import_name, pip_name))

    if missing_libs:
        msg = "⚠️ 缺少以下依赖库，请先安装：\n"
        for imp_name, pip_name in missing_libs:
            msg += f"- {imp_name} → 安装命令：pip install {pip_name}\n"
        QMessageBox.critical(None, "依赖缺失", msg)
        return False
    return True


# -------------------------- 脚本检查 --------------------------
def check_script_syntax(script_path: str) -> Tuple[bool, str]:
    """检查Python脚本语法（支持多编码：UTF-8/GBK/GB2312）"""
    if not os.path.exists(script_path):
        return False, f"脚本文件不存在：{script_path}"

    # 尝试多种编码读取
    encodings = ['utf-8', 'gbk', 'gb2312']
    code = ""
    for encoding in encodings:
        try:
            with open(script_path, 'r', encoding=encoding) as f:
                code = f.read()
            break
        except UnicodeDecodeError:
            continue
    else:
        return False, "脚本编码不支持（仅支持UTF-8/GBK/GB2312）"

    # 语法解析
    try:
        ast.parse(code)
        return True, "✅ 语法检查通过"
    except SyntaxError as e:
        return False, f"❌ 语法错误（行{e.lineno}）：{e.msg}"
    except Exception as e:
        return False, f"❌ 检查失败：{str(e)}"


def check_script_dependencies(script_path: str) -> Tuple[bool, str]:
    """检查脚本依赖的第三方库（排除Python标准库）"""
    if not os.path.exists(script_path):
        return False, f"脚本文件不存在：{script_path}"

    # 读取脚本并解析导入语句
    encodings = ['utf-8', 'gbk', 'gb2312']
    code = ""
    for encoding in encodings:
        try:
            with open(script_path, 'r', encoding=encoding) as f:
                code = f.read()
            break
        except UnicodeDecodeError:
            continue
    else:
        return False, "脚本编码不支持（仅支持UTF-8/GBK/GB2312）"

    # 解析导入的库
    tree = ast.parse(code)
    imports: Set[str] = set()
    # Python标准库列表（排除这些库的检查）
    std_libs = {
        'os', 'sys', 'time', 'datetime', 'logging', 'ast', 'sqlite3',
        'tempfile', 'subprocess', 'json', 'csv', 're', 'math', 'random'
    }

    # 提取import和from...import语句
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                imports.add(alias.name.split('.')[0])  # 取顶层模块名（如import requests → requests）
        elif isinstance(node, ast.ImportFrom) and node.module:
            imports.add(node.module.split('.')[0])  # 如from flask import Flask → flask

    # 过滤标准库，检查第三方库
    third_party_libs = imports - std_libs
    missing_libs: list[str] = []
    for lib in third_party_libs:
        try:
            importlib.import_module(lib)
        except ImportError:
            missing_libs.append(lib)

    if missing_libs:
        install_cmd = f"pip install {' '.join(missing_libs)}"
        return False, f"❌ 缺少依赖库：{', '.join(missing_libs)}\n安装命令：{install_cmd}"
    return True, "✅ 所有依赖库检查通过"


# -------------------------- Cron解析 --------------------------
def calculate_next_run(cron_expr: str) -> Optional[str]:
    """
    根据Cron表达式计算下次运行时间（支持*和固定数值，不支持范围/步长）
    Cron格式：分 时 日 月 周（0=周一，6=周日）
    返回：YYYY-MM-DD HH:MM:SS 或 None（解析失败）
    """
    try:
        parts = cron_expr.strip().split()
        if len(parts) != 5:
            logger.warning(f"Cron表达式格式错误（需5部分）：{cron_expr}")
            return None

        minute, hour, day, month, weekday = parts
        now = datetime.datetime.now()

        # 解析Cron字段值（*表示当前值，否则转为整数并校验范围）
        def parse_cron_field(field: str, current: int, min_val: int, max_val: int) -> int:
            if field == '*':
                return current
            try:
                val = int(field)
                if min_val <= val <= max_val:
                    return val
                else:
                    logger.warning(f"Cron字段值超出范围[{min_val}-{max_val}]：{field}")
                    return current
            except ValueError:
                logger.warning(f"Cron字段值无效（需整数或*）：{field}")
                return current

        # 解析各字段
        parsed_minute = parse_cron_field(minute, now.minute, 0, 59)
        parsed_hour = parse_cron_field(hour, now.hour, 0, 23)
        parsed_day = parse_cron_field(day, now.day, 1, 31)
        parsed_month = parse_cron_field(month, now.month, 1, 12)
        parsed_weekday = parse_cron_field(weekday, now.weekday(), 0, 6)  # 0=周一

        # 构造基础下次运行时间
        next_run = now.replace(
            minute=parsed_minute,
            hour=parsed_hour,
            day=parsed_day,
            month=parsed_month,
            second=0,
            microsecond=0
        )

        # 调整星期（如果星期不匹配，加N天直到匹配）
        if next_run.weekday() != parsed_weekday:
            days_ahead = (parsed_weekday - next_run.weekday()) % 7
            next_run += datetime.timedelta(days=days_ahead)

        # 如果计算出的时间已过去（如当前10点，Cron是9点），加1天
        if next_run <= now:
            next_run += datetime.timedelta(days=1)

        # 校验日期有效性（如2月30日自动调整）
        try:
            next_run.strftime('%Y-%m-%d %H:%M:%S')  # 触发无效日期异常
        except ValueError:
            next_run = next_run.replace(day=1) + datetime.timedelta(months=1)  # 跳到下月1日

        return next_run.strftime('%Y-%m-%d %H:%M:%S')
    except Exception as e:
        logger.error(f"Cron表达式解析失败：{str(e)}，表达式：{cron_expr}")
        return None


# -------------------------- 录屏工具 --------------------------
def get_recording_path(task_id: int, start_time: str) -> str:
    """生成录屏文件路径（统一目录+任务ID+时间戳，避免重复）"""
    # 确保录屏目录存在
    if not os.path.exists(RECORDING_DIR):
        os.makedirs(RECORDING_DIR)

    # 时间戳格式：YYYYMMDD_HHMMSS
    time_str = start_time.replace('-', '').replace(' ', '_').replace(':', '')
    # 使用os.path.join确保路径分隔符正确，然后转为绝对路径
    recording_path = os.path.join(RECORDING_DIR, f"task_{task_id}_{time_str}.mp4")
    return os.path.abspath(recording_path)


# -------------------------- 日志清理 --------------------------
def clear_expired_logs(keep_days: int = DEFAULT_LOG_KEEP_DAYS) -> Tuple[int, int]:
    """
    清理过期日志（任务日志+录屏文件）
    返回：(删除日志条数, 删除录屏文件数)
    """
    from db.operations import get_expired_logs, delete_log_by_id  # 避免循环导入

    cutoff_date = (datetime.datetime.now() - datetime.timedelta(days=keep_days)).strftime('%Y-%m-%d %H:%M:%S')
    expired_logs = get_expired_logs(cutoff_date)  # 从数据库获取过期日志

    deleted_log_count = 0
    deleted_recording_count = 0

    for log in expired_logs:
        log_id, recording_path = log['id'], log['screen_recording_path']

        # 1. 删除录屏文件（如果存在）
        if recording_path and os.path.exists(recording_path):
            try:
                os.remove(recording_path)
                deleted_recording_count += 1
                logger.info(f"删除过期录屏文件：{recording_path}")
            except Exception as e:
                logger.error(f"删除录屏文件失败：{recording_path}，错误：{str(e)}")

        # 2. 删除数据库日志记录
        if delete_log_by_id(log_id):
            deleted_log_count += 1
            logger.info(f"删除过期日志：log_id={log_id}")

    return deleted_log_count, deleted_recording_count


def fix_existing_recording_paths():
    """修复数据库中已有的录屏文件路径（从相对路径转换为绝对路径）"""
    try:
        from config.constants import RECORDING_DIR
        logs = get_all_task_logs()
        fixed_count = 0
        
        for log in logs:
            log_id = log['id']
            recording_path = log.get('screen_recording_path', '')
            
            if recording_path and not os.path.isabs(recording_path):
                # 提取文件名，然后用RECORDING_DIR重新构建绝对路径
                file_name = os.path.basename(recording_path)
                new_path = os.path.join(RECORDING_DIR, file_name)
                
                if os.path.exists(new_path):
                    update_task_log(log_id, screen_recording_path=new_path)
                    fixed_count += 1
                    logger.info(f"修复录屏路径：log_id={log_id}, 从'{recording_path}'到'{new_path}'")
        
        if fixed_count > 0:
            logger.info(f"成功修复{fixed_count}条录屏路径记录")
    except Exception as e:
        logger.error(f"修复录屏路径时出错：{str(e)}")